﻿using SQLite.CodeFirst.Extensions;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Globalization;
using System.Linq;
using SQLite.CodeFirst.Builder.NameCreators;

namespace SQLite.CodeFirst.Convention
{
    /// <summary>
    /// Renames the indicies, generated by the 
    /// <see cref="ForeignKeyIndexConvention"/>-Convention, to match the scheme: "IX_{TableName}_{PropertyName}".
    /// This is necessary because in SQLite an index-name must be unique.
    /// Must be added right after the <see cref="ForeignKeyIndexConvention"/>-Convention.
    /// </summary>
    internal class SqliteForeignKeyIndexConvention : IStoreModelConvention<AssociationType>
    {
        private const string IndexAnnotationName = "http://schemas.microsoft.com/ado/2013/11/edm/customannotation:Index";

        public virtual void Apply(AssociationType item, DbModel model)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            if (model == null)
            {
                throw new ArgumentNullException("model");
            }
            if (item.Constraint == null)
            {
                return;
            }

            foreach (var edmProperty in item.Constraint.ToProperties)
            {
                var annotation = GetAnnotation(edmProperty.MetadataProperties, IndexAnnotationName);
                if (annotation == null)
                {
                    continue;
                }

                string tableName = item.Constraint.ToRole.GetEntityType().GetTableName();

                // Special treatment for composite primary keys
                string propertyName = item.Constraint.FromProperties.Count > 1 
                    ? String.Join("_", item.Constraint.ToProperties) 
                    : edmProperty.Name;

                // The original attribute is removed. The none-ForeignKeyIndicies will be remained and readded without any modification
                // and the foreignKeyIncidies will be readded with the correct name.
                edmProperty.RemoveAnnotation(IndexAnnotationName);

                // The schema for the automatically generated index name is "IX_{TableName}_{PropertyName}"
                var noneForeignKeyIndicies = annotation.Indexes.Where(index => index.Name != "IX_" + propertyName);
                var newIndexAnnotation = new IndexAnnotation(noneForeignKeyIndicies);

                // The schema for a FK index, which is generated by the Entity Framework, is "IX_{PropertyName}"
                var foreignKeyIndicies = annotation.Indexes.Where(index => index.Name == "IX_" + propertyName);
                for (int i = 0; i < foreignKeyIndicies.Count(); i++)
                {
                    IndexAnnotation foreignKeyIndexAnnotation = CreateIndexAnnotation(tableName, propertyName, i);
                    newIndexAnnotation = (IndexAnnotation)newIndexAnnotation.MergeWith(foreignKeyIndexAnnotation);
                }

                edmProperty.AddAnnotation(IndexAnnotationName, newIndexAnnotation);
            }
        }

        private static IndexAnnotation CreateIndexAnnotation(string tableName, string propertyName, int count)
        {
            var indexName = IndexNameCreator.CreateName(tableName, propertyName);

            // If there are two Indicies on the same property, the count is added.
            // In SQLite an Index name must be global unique.
            // To be honest, it should never happen. But because its possible by using the API, it should be covered.
            if (count > 0)
            {
                indexName = String.Format(CultureInfo.InvariantCulture, "{0}_{1}", indexName, count);
            }

            var indexAttribute = new IndexAttribute(indexName);
            return new IndexAnnotation(indexAttribute);
        }

        private static IndexAnnotation GetAnnotation(IEnumerable<MetadataProperty> metadataProperties, string name)
        {
            foreach (MetadataProperty metadataProperty in metadataProperties)
            {
                if (metadataProperty.Name.Equals(name, StringComparison.Ordinal))
                {
                    return (IndexAnnotation)metadataProperty.Value;
                }
            }
            return null;
        }
    }
}